﻿using System;
using System.Collections.Generic;
using System.Reflection;

namespace Murry.DataStore
{
    /// <summary>
    /// Base class for Read-only DataStores.
    /// </summary>
    /// <typeparam name="TInternalDatabase">Internal database type.</typeparam>
    public abstract class ReadOnlyDataStore<TInternalDatabase> : IReadOnlyDataStore
    {
        /// <summary>
        /// Internal Database of type TInternalDatabase.
        /// </summary>
        protected TInternalDatabase Database { get; set; }

        /// <summary>
        /// Is object type supported by this DataStore.
        /// </summary>
        /// <typeparam name="TInterface">Interface Type</typeparam>
        /// <returns>True if supported</returns>
        public virtual bool IsSupported<TInterface>()
        {
            return Entities.IsSupported(GetType(), typeof(TInterface));
        }

        /// <summary>
        /// Find all objects of a given type.
        /// </summary>
        /// <typeparam name="TInterface">Interface type</typeparam>
        /// <returns>Objects</returns>
        public virtual IEnumerable<TInterface> FindAll<TInterface>()
        {
            var implementedType = Entities.GetImplementedEntityType(GetType(), typeof (TInterface));

            var findAll = implementedType.GetMethod(
                "FindAll",
                BindingFlags.Static | BindingFlags.Public,
                null,
                new [] {typeof (TInternalDatabase)},
                null);

            if (findAll == null)
            {
                throw new InvalidOperationException("FilterAll not supported for this type.");
            }

            return (IEnumerable<TInterface>)findAll.Invoke(null, new object[] { Database });
        }

        /// <summary>
        /// Find object by ID.
        /// </summary>
        /// <typeparam name="TInterface">Interface type</typeparam>
        /// <param name="id">Object ID</param>
        /// <returns>Object</returns>
        public virtual TInterface FindById<TInterface>(string id)
        {
            var implementedType = Entities.GetImplementedEntityType(GetType(), typeof(TInterface));

            var findById = implementedType.GetMethod(
                "FindById",
                BindingFlags.Static | BindingFlags.Public,
                null,
                new [] { typeof(TInternalDatabase), typeof(string) },
                null);

            if (findById == null)
            {
                throw new InvalidOperationException("FindById not supported for this type.");
            }

            return (TInterface)findById.Invoke(null, new object[] { Database, id });
        }

        /// <summary>
        /// Get a filtered list of objects.
        /// </summary>
        /// <typeparam name="TInterface">Interface type</typeparam>
        /// <param name="filter">Filter</param>
        /// <returns>Objects</returns>
        public virtual IEnumerable<TInterface> FindByFilter<TInterface>(object filter)
        {
            var implementedType = Entities.GetImplementedEntityType(GetType(), typeof(TInterface));

            var findByFilter = implementedType.GetMethod(
                "FindByFilter", 
                BindingFlags.Static | BindingFlags.Public, 
                null, 
                new [] {typeof(TInternalDatabase), filter.GetType()}, 
                null);

            if (findByFilter == null)
            {
                throw new InvalidOperationException("Filter not supported for this type.");
            }

            return (IEnumerable<TInterface>) findByFilter.Invoke(null, new [] {Database, filter});
        }
    }
}
